#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>

#include "librc.h"

#define USB_DEVICES_FILE	"/proc/bus/usb/devices"
#define USB_DEVPATH_INFO	"/proc/bus/usb/devpath"

#define BUF_LEN		512

/// read string parameter with name 'pname' from vbuf
static char *get_str_par(char *vbuf, const char *pname)
{
	char *ptr;

	if ((ptr = strstr(vbuf, pname)) == NULL)
		return NULL;
	ptr += strlen(pname);
	return strdup(ptr);
}

/// read numeric parameter with name 'pname' from vbuf
static int get_numeric_par(char *vbuf, const char *pname, int base, char **num_str)
{
	char *ptr;

	if ((ptr = strstr(vbuf, pname)) == NULL)
		return -1;
	ptr += strlen(pname);
	if (num_str)
		*num_str = ptr;
	return strtol(ptr, NULL, base);
}

/// read decimal parameter with name 'pname' from vbuf
#define get_int_par(vbuf, pname)	get_numeric_par(vbuf, pname, 10, NULL)
/// read hex parameter with name 'pname' from vbuf
#define get_hex_par(vbuf, pname)	get_numeric_par(vbuf, pname, 16, NULL)

/// get list of usb devices from driver
dev_usb_t *usb_get_list()
{
	const char *fn_dev = USB_DEVICES_FILE;
	FILE *fp = NULL;
	char *vbuf = NULL, *ptr;
	dev_usb_t *dev = NULL;
	dev_usb_t *last = NULL;
	dev_usb_t *list = NULL;
	dev_usb_t *tmp;
	ifs_usb_elem_t *i = NULL, *l = NULL;
	int skip = 0;

	if ((vbuf = malloc(BUF_LEN)) == NULL) return NULL;

	if ((fp = fopen(fn_dev, "rt")) == NULL) {
		free(vbuf);
		return NULL;
	}

	//  Citation of kernel/Documentation/usb/proc_usb_info.txt
	//
	//  Each line is tagged with a one-character ID for that line:
	//
	//  T = Topology (etc.)
	//  B = Bandwidth (applies only to USB host controllers, which are
	//      virtualized as root hubs)
	//  D = Device descriptor info.
	//  P = Product ID info. (from Device descriptor, but they won't fit
	//      together on one line)
	//  S = String descriptors.
	//  C = Configuration descriptor info. (* = active configuration)
	//  I = Interface descriptor info.
	//  E = Endpoint descriptor info.
	while (fgets(vbuf, BUF_LEN, fp) != 0) {
		if ((ptr = strchr(vbuf,'\n')))
			*ptr = 0;

		if (skip == 1 && (*vbuf != 'I' || *vbuf != 'E'))
			continue;
		else
			skip = 0;

		if (dev == NULL && *vbuf != 'T')
			continue;

//		dprintf("%s", vbuf);

		switch (*vbuf) {
			case 'T':
				if ( (dev = malloc(sizeof(dev_usb_t))) == NULL ||
					memset(dev,0,sizeof(dev_usb_t)) == NULL) {
					list = NULL;
					goto out;
				}

				dev->type = TYPE_NONE;
				dev->bus = get_int_par(vbuf, "Bus=");
				dev->parent = get_int_par(vbuf, "Prnt=");
				dev->port = get_int_par(vbuf, "Port=");
				dev->number = get_int_par(vbuf, "Dev#=");

				if (list == NULL)
					list = dev;
				else {
					last->next = dev;
					dev->prev = last;
				}
				last = dev;
				// get location
				if (dev->parent != 0) {
					tmp = dev->prev;
					while (tmp && tmp->number != dev->parent)
						tmp = tmp->prev;
					if (tmp) {
						if (tmp->loc[0])
							sprintf(dev->loc, "%s.%d", tmp->loc, dev->port + 1);
						else
							sprintf(dev->loc, "%d", dev->port + 1);
					}
				}
				break;
			case 'D':
				dev->cls = get_hex_par(vbuf, "Cls=");
				dev->sub = get_hex_par(vbuf, "Sub=");
				dev->prot = get_hex_par(vbuf, "Prot=");
				break;
			case 'P':
				dev->vid = get_hex_par(vbuf, "Vendor=");
				dev->pid = get_hex_par(vbuf, "ProdID=");
				break;
			case 'S':
				if (dev->manuf == NULL)
					dev->manuf = get_str_par(vbuf, "Manufacturer=");
				if (dev->prod == NULL)
					dev->prod = get_str_par(vbuf, "Product=");
				break;
			case 'C':
				if (vbuf[2] == '*') //default configuration
					dev->num_ifs = get_int_par(vbuf, "#Ifs=");
				else
					skip = 1;
				break;
			case 'I':
				if ((i = malloc(sizeof(ifs_usb_elem_t))) == NULL) {
					list = NULL;
					goto out;
				}
				i->number = get_int_par(vbuf, "If#=");
				i->endpoints = get_int_par(vbuf, "#EPs=");
				i->cls = get_hex_par(vbuf, "Cls=");
				i->sub = get_hex_par(vbuf, "Sub=");
				i->prot = get_hex_par(vbuf, "Prot=");
				i->driver = get_str_par(vbuf, "Driver=");
				i->next = NULL;
				if (dev->ifs == NULL)
					dev->ifs = i;
				else {
					if (l)
						l->next = i;
				}
				l = i;
				break;
			case 'B': 
				break;
			default:
				break;
		}
	}

out:
	free(vbuf);
	fclose(fp);
	return list;
}

void usb_free_dev(dev_usb_t *dev)
{
	ifs_usb_elem_t *t;

	free(dev->manuf);
	free(dev->prod);
	while (dev->ifs) {
		t = dev->ifs->next;
		free(dev->ifs->driver);
		free(dev->ifs);
		dev->ifs = t;
	}
	free(dev);
}

/// free memory from dev_usb_t list
void usb_free_list(dev_usb_t *list)
{
	dev_usb_t *dev = list, *tmp;

	while (dev) {
		tmp = dev->next;
		usb_free_dev(dev);
		dev = tmp;
	}
}

/*dev_usb_t *usb_copy_dev(dev_usb_t *dev)
{
	dev_usb_t *new_dev = NULL;
	ifs_usb_elem_t *i = NULL, *l = NULL;

	if ((new_dev = malloc(sizeof(dev_usb_t))) == NULL ||
	    memset(new_dev, 0, sizeof(dev_usb_t)) == NULL) {
		new_dev = NULL;
		goto out;
	}

	memcpy(new_dev, dev, sizeof(dev_usb_t));
	new_dev->manuf = strdup(dev->manuf);
	new_dev->prod = strdup(dev->prod);
	new_dev->prev = new_dev->next = NULL;

	new_dev->ifs = NULL;
	while (dev->ifs) {
		if ((i = malloc(sizeof(ifs_usb_elem_t))) == NULL) {
			new_dev = NULL;
			goto out;
		}
		memcpy(i, dev->ifs, sizeof(ifs_usb_elem_t));
		i->driver = strdup(dev->ifs->driver);
		i->next = NULL;
		if (new_dev->ifs == NULL)
			new_dev->ifs = i;
		else {
			if (l)
				l->next = i;
		}
		l = i;
		dev->ifs = dev->ifs->next;
	}

out:
	return new_dev;
}

dev_usb_t *usb_get_dev_by_loc(const char *loc)
{
	dev_usb_t *t = NULL, *dev;

	if (!loc || !*loc)
		return NULL;

	dev_usb_t *dev_list = usb_get_list();

	for (dev = dev_list; dev; dev = dev->next) {
		if (!strcmp(dev->loc, loc))
			t = usb_copy_dev(dev);
	}

	usb_free_list(dev_list);

	return t;
}*/